/*LICENSE_BEGIN
 * ******************************************************************
 * All original parts of SoccerScope2003 have copyright (2003) by
 * Team YowAI, represented by Mr. Shuhei Shiota
 * The Department of Creative Informatics
 * The Graduate School of Information Science and Technology
 * The University of Tokyo
 * 
 * Additional classes (except libraries in /lib) were developed by 
 * the Dainamite Robocup-Team of the DAI-Labor, TU-Berlin 
 * (www.dai-labor.de)
 * 
 * Extended/new classes/packages are:
 *   soccerscope.analyzer.LogfileAnalyzer
 *   soccerscope.daistatistic.*
 *   soccerscope.daistatistic.charts.*
 *   soccerscope.daistatistic.tools.*
 *   soccerscope.db.*
 *   soccerscope.dialog.*
 *   soccerscope.model.PlayerWorldModel
 *   soccerscope.model.SimplePlayer
 *   soccerscope.net.AgentConnector
 *   soccerscope.training.*
 *   soccerscope.training.conditions.*
 * ******************************************************************
LICENSE_END*/
package soccerscope.view.layer;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BoxLayout;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JToggleButton;

import robocup.component.tactics.AbstractState.STATES;
import soccerscope.model.ClientScene;
import soccerscope.model.ColorDB;
import soccerscope.model.HeteroParam;
import soccerscope.model.Param;
import soccerscope.model.PlayerWorldModel;
import soccerscope.model.SimplePlayer;
import soccerscope.model.SoccerObjectID;
import soccerscope.model.WorldModel;
import soccerscope.util.geom.Point2f;
import soccerscope.util.geom.Tuple2f;
import soccerscope.view.FieldPane;
import soccerscope.view.SoccerObjectSelectObserver;

public class AgentWorldModelLayer extends Layer implements SoccerObjectSelectObserver,
        SoccerObjectID, LayerBooleanObserver, ActionListener {

    public final static String SETS_ALL_ON_TEXT = "Sets all on";
    public final static String SETS_ALL_OFF_TEXT = "Sets all off";
    
    private LayerBoolean onCB;

    private LayerBoolean teammatesCB;
    private LayerBoolean nextTeammateCB;
    private LayerBoolean opponentCB;
    private LayerBoolean nextOpponentCB;

    private LayerBoolean playerCB;

    private LayerBoolean bestPosCB;

    private LayerBoolean ballCB;

    private LayerBoolean ipCB;

    private LayerBoolean oppIPCB;

    private LayerBoolean velocityCB;

    private LayerBoolean offsideLineCB;

    private LayerBoolean staminaCB;
    
    private JMenuItem allMenu;

    public AgentWorldModelLayer(FieldPane fieldPane, boolean enable) {
        super(fieldPane, enable);

        onCB = new LayerBoolean("Layer on", this.enable);
        onCB.addObserver(new AgentWorldModelLayerToggleListener());
        
        teammatesCB = new LayerBoolean("Teammates", true);
        teammatesCB.addObserver(this);
        nextTeammateCB = new LayerBoolean("NextTeammates", false);
        nextTeammateCB.addObserver(this);
        opponentCB = new LayerBoolean("Opponents", true);
        opponentCB.addObserver(this);
        nextOpponentCB = new LayerBoolean("NextOpponents", false);
        nextOpponentCB.addObserver(this);

        playerCB = new LayerBoolean("Players", true);
        playerCB.addObserver(this);
        bestPosCB = new LayerBoolean("Best Position", false);
        bestPosCB.addObserver(this);
        ballCB = new LayerBoolean("Ball", true);
        ballCB.addObserver(this);
        ipCB = new LayerBoolean("Ball intercept point", false);
        ipCB.addObserver(this);
        oppIPCB = new LayerBoolean("Opponent intercept point", false);
        oppIPCB.addObserver(this);
        velocityCB = new LayerBoolean("Velocity", false);
        velocityCB.addObserver(this);
        offsideLineCB = new LayerBoolean("Offside line", true);
        offsideLineCB.addObserver(this);
        staminaCB = new LayerBoolean("Staminas", false);
        staminaCB.addObserver(this);
    }

    @Override
    public String getLayerName() {
        return "AgentWorldModel";
    }

    @Override
    public JPanel getConfigPanel() {
        JPanel panel = new JPanel();

        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
        panel.add(teammatesCB.getCheckBox());
        panel.add(nextTeammateCB.getCheckBox());
        panel.add(opponentCB.getCheckBox());
        panel.add(nextOpponentCB.getCheckBox());

        panel.add(playerCB.getCheckBox());
        panel.add(bestPosCB.getCheckBox());
        panel.add(ballCB.getCheckBox());
        panel.add(ipCB.getCheckBox());
        panel.add(oppIPCB.getCheckBox());
        panel.add(velocityCB.getCheckBox());
        panel.add(offsideLineCB.getCheckBox());
        panel.add(staminaCB.getCheckBox());

        return panel;
    }


    @Override
    public JToggleButton createJToggleButton() {
        JToggleButton button = onCB.getToggleButton();
        return button;
    }

    private class AgentWorldModelLayerToggleListener implements LayerBooleanObserver {
        /**
         * is called when a bool has changed.
         */
        public void boolChanged() {
            enable = onCB.getValue();
        }
    }
    
    public JMenu createJCheckBoxMenuItem() {
        JMenu menu = new JMenu(getLayerName());
        
        menu.add(onCB.getMenuItem());
        menu.addSeparator();
        allMenu = new JMenuItem(SETS_ALL_ON_TEXT);
        allMenu.addActionListener(this);
        menu.add(allMenu);
        menu.addSeparator();
        menu.add(teammatesCB.getMenuItem());
        menu.add(nextTeammateCB.getMenuItem());
        menu.add(opponentCB.getMenuItem());
        menu.add(nextOpponentCB.getMenuItem());
        menu.addSeparator();
        menu.add(playerCB.getMenuItem());
        menu.add(bestPosCB.getMenuItem());
        menu.add(ballCB.getMenuItem());
        menu.add(ipCB.getMenuItem());
        menu.add(oppIPCB.getMenuItem());
        menu.add(velocityCB.getMenuItem());
        menu.add(offsideLineCB.getMenuItem());
        menu.add(staminaCB.getMenuItem());
        
        return menu;
    }

    /**
     * is called, when the menu item "Sets all ..." was clicked.
     * @param e the ActionEvent
     */
    public void actionPerformed(ActionEvent e) {
        if (allMenu.getText().compareTo(SETS_ALL_ON_TEXT) == 0) {
            teammatesCB.setSelected(true);
            nextTeammateCB.setSelected(true);
            opponentCB.setSelected(true);
            nextOpponentCB.setSelected(true);
            playerCB.setSelected(true);
            bestPosCB.setSelected(true);
            ballCB.setSelected(true);
            ipCB.setSelected(true);
            oppIPCB.setSelected(true);
            velocityCB.setSelected(true);
            offsideLineCB.setSelected(true);
            staminaCB.setSelected(true);
            allMenu.setText(SETS_ALL_OFF_TEXT);
        } else {
            teammatesCB.setSelected(false);
            nextTeammateCB.setSelected(false);
            opponentCB.setSelected(false);
            nextOpponentCB.setSelected(false);
            playerCB.setSelected(false);
            bestPosCB.setSelected(false);
            ballCB.setSelected(false);
            ipCB.setSelected(false);
            oppIPCB.setSelected(false);
            velocityCB.setSelected(false);
            offsideLineCB.setSelected(false);
            staminaCB.setSelected(false);
            allMenu.setText(SETS_ALL_ON_TEXT);
        }
    }
    
    
    /**
     * is called when a bool has changed.
     */
    public void boolChanged() {
        if (    teammatesCB.getValue()
                && nextTeammateCB.getValue()
                && opponentCB.getValue()
                && nextOpponentCB.getValue()
                && playerCB.getValue()
                && bestPosCB.getValue()
                && ballCB.getValue()
                && ipCB.getValue()
                && oppIPCB.getValue()
                && velocityCB.getValue()
                && offsideLineCB.getValue()
                && staminaCB.getValue()) {
            allMenu.setText(SETS_ALL_OFF_TEXT);
        } else {
            allMenu.setText(SETS_ALL_ON_TEXT);
        }
    }

//    private LocalScene clientScene = new LocalScene();

    private float ballMagnify;

    private float playerMagnify;

    private boolean[] isSelected = new boolean[Param.TOTAL_PLAYER];

    public void selectSoccerObject(int id, boolean flag) {
        if (id >= TOTAL_PLAYER) {
            return;
        }
        this.isSelected[id] = flag;
    }

    @Override
    public void draw(Graphics g) {
        if (!this.enable || this.fieldPane.getScene().time <= 0)
            return;

        this.ballMagnify = this.fieldPane.getBallMagnify();
        this.playerMagnify = this.fieldPane.getPlayerMagnify();

        Graphics2D g2 = (Graphics2D) g;
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f));

        // get data for cycle we have to draw...
        ClientScene thisScene = this.fieldPane.getScene();
        for (int i = 0; i < Param.TOTAL_PLAYER; i++) {
            if (this.isSelected[i]) {
                int c = 0;
                PlayerWorldModel pwm = thisScene.player[i].getWorldModel();
                if (pwm == null) {
                    continue;
                }
                while (pwm == null || pwm.positionX == -9999) {
                    try {
                        thisScene = WorldModel.getSceneSet().elementAt(
                                this.fieldPane.getScene().time - c);
                    } catch (IndexOutOfBoundsException e) {
                        break;
                    }
                    pwm = thisScene.player[i].getWorldModel();
                    c++;
                }
                if (pwm == null) {
                    continue;
                }

                drawPlayers(pwm, i < MAX_PLAYER, g2);
                
                if (ballCB.getValue()) {
                    drawBall(pwm, g2);
                }
                if (ipCB.getValue()) {
                    drawIP(pwm, g2);
                }
                if (oppIPCB.getValue()) {
                    drawOppIP(pwm, g2);
                }
                if (offsideLineCB.getValue()) {
                    drawOffsideLine(pwm, g2);
                }
            }
        }

        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
    }

    private Point2f drawPoint = new Point2f();

    private Point2f drawSize = new Point2f();

    private Point2f drawOffset = new Point2f();

    private Point2f upperOffside = new Point2f();

    private Point2f lowerOffside = new Point2f();

    private boolean showOnlyPositioning = false;

    /**
     * Returns the color for a player
     * @param player the player
     * @param leftSide true if the player play from left to right
     * @return the color
     */
    private Color getDrawColor(SimplePlayer player, boolean leftSide) {
        Color drawColor = Color.MAGENTA;

        if (!leftSide)
            drawColor = Color.RED;
        else
            drawColor = Color.YELLOW;

        if (player.positionIsReliable == false && !leftSide) {
            drawColor = Color.BLACK;
        }
        if (player.positionIsReliable == false && leftSide) {
            drawColor = Color.WHITE;
        }

        if (player.isGoalie && !leftSide)
            drawColor = Color.BLUE;

        if (player.isGoalie && leftSide)
            drawColor = Color.GREEN;

        return drawColor;
    }
    
    private void drawPlayer(SimplePlayer player, Graphics g, boolean leftSide, boolean next, int playerNumber) {
        if (showOnlyPositioning || player == null) {
            return;
        }

        float playerR = HeteroParam.get(player.playerType).player_size * this.playerMagnify;


        Color drawColor = getDrawColor(player, leftSide);
        if (next)
            drawColor = Color.BLACK;

        // draw mode
        g.setColor(Color.black);

        Point2f pos = new Point2f((float) player.positionY, (float) player.positionX);
        if (playerCB.getValue()) {
            fillCircle(g, pos, playerR);
    
            // draw body angle
            g.setColor(drawColor);
            this.drawSize.set(playerR, playerR);
            this.drawPoint.sub(pos, this.drawSize);
            this.drawSize.scale(2);
            fillArc(g, this.drawPoint, this.drawSize, -((int) player.simpleBody + 90), 180);
    
            // draw body
            g.setColor(drawColor);
            drawCircle(g, pos, playerR);
    
            // draw unum
            if (next)
                g.setColor(Color.black);
            else
                g.setColor(Color.white);
    
            if (!showOnlyPositioning) {
                this.drawOffset.set(0, -playerR);
                this.drawPoint.add(pos, this.drawOffset);
                drawString(g, Integer.toString(playerNumber), this.drawPoint);
    
                // draw interception time
                if (!next) {
                    String interceptString = (player.ballInterceptionTime == Integer.MAX_VALUE)
                            ? "x" : Integer.toString(player.ballInterceptionTime);
                    g.setColor(Color.white);
                    this.drawOffset.set(playerR, 0);
                    this.drawPoint.add(pos, this.drawOffset);
                    drawString(g, interceptString, this.drawPoint);
                }
    
                // draw playerType
                if (!next) {
                    String playerTypeString = Integer.toString(player.playerType);
                    g.setColor(Color.lightGray);
                    this.drawOffset.set(-playerR, playerR);
                    this.drawPoint.add(pos, this.drawOffset);
                    drawString(g, playerTypeString, this.drawPoint);
                }
    
                // draw role number
                if (!next) {
                    String roleString = (player.roleNumber < 0) ? "x" : Integer
                            .toString(player.roleNumber);
                    g.setColor(Color.white);
                    this.drawOffset.set(0, playerR + (float) 0.3);
                    this.drawPoint.add(pos, this.drawOffset);
                    drawString(g, roleString, this.drawPoint);
                }
            }
        }
        if (bestPosCB.getValue()) {
            if (!next && player.bestPos != null) {
                g.setColor(Color.ORANGE);
                drawLine(g, pos, player.bestPos);
                g.setColor(Color.MAGENTA);
                fillCircle(g, player.bestPos, 0.5f);
            }
        }

        // if(!next && player.bestPos !=null){
        // String bestPosString = player.unum + " " + player.roleNumber;
        // g.setColor(Color.black);
        // drawString(g, bestPosString, player.bestPos);
        // }

        // draw predicted state
        if (next && !showOnlyPositioning) {
            g.setColor(Color.BLACK);
            this.drawOffset.set(playerR, 0);
            this.drawPoint.add(pos, this.drawOffset);
            String state = player.lastState == STATES.DUMMY_STATE ? "" : player.lastState.toString();
            if (state.length() == 0) {
                drawString(g, "NO STATE", this.drawPoint);
            } else {
                drawString(g, state, this.drawPoint);
            }
        }

        // draw player velocity
        if (!next && velocityCB.getValue()) {
            g.setColor(Color.red);
            Point2f newPos = new Point2f(pos);
            newPos.add(new Point2f((float) player.speedY, (float) player.speedX));
            drawLine(g, pos, newPos);
            drawCircle(g, newPos, playerR);
        }
        
        // draw stamina
        if (staminaCB.getValue()) {
            g.setColor(Color.black);
            Tuple2f size = new Tuple2f(playerR / 2, playerR * 2);
            Point2f drawPoint = new Point2f(pos);
            drawPoint.add(new Point2f((float) (playerR + size.getX()), (float) (playerR - size.getY())));
            fillRect(g, drawPoint, size);
    
            if (player.stamina > STAMINA_MAX * EFFORT_INC_THR)
                g.setColor(Color.green);
            else if (player.stamina > STAMINA_MAX * EFFORT_DEC_THR)
                g.setColor(Color.getHSBColor((player.stamina - STAMINA_MAX
                        * EFFORT_DEC_THR)
                        / (3 * (STAMINA_MAX * (EFFORT_INC_THR - EFFORT_DEC_THR))), 1.0f,
                        1.0f));
            else
                g.setColor(Color.red);
    
            size.set(playerR / 2, playerR * 2 * (player.stamina / STAMINA_MAX));
            fillRect(g, drawPoint, size);
        }
    }

    /**
     * draws all players of if they are in the player world model
     * @param pwm the player world model
     * @param leftSide true if the player ist playing on left side
     * @param g the graphical context
     */
    private void drawPlayers(PlayerWorldModel pwm, boolean leftSide, Graphics g) {
        for (int i = 0; i < Param.MAX_PLAYER; i++) {
            if (teammatesCB.getValue()) {
                drawPlayer(pwm.teammates[i], g, leftSide, false, i + 1);
            }
            if (nextTeammateCB.getValue()) {
                drawPlayer(pwm.nextTeammates[i], g, leftSide, true, i + 1);
            }
            if (opponentCB.getValue()) {
                drawPlayer(pwm.opponents[i], g, !leftSide, false, i + 1);
            }
            if (nextOpponentCB.getValue()) {
                drawPlayer(pwm.nextOpponents[i], g, !leftSide, true, i + 1);
            }            
        }
    }

    private void drawBall(PlayerWorldModel pwm, Graphics g) {
        float ballR = Param.BALL_SIZE * this.ballMagnify;
        Point2f ballPos = new Point2f((float) pwm.ballPositionY, (float) pwm.ballPositionX);
        g.setColor(ColorDB.getColor("ball"));
        fillCircle(g, ballPos, ballR);

        // draw ball velocity
        if (velocityCB.getValue()) {
            g.setColor(Color.red);
            Point2f newPos = new Point2f(ballPos);
            newPos.add(new Point2f(new Point2f((float) pwm.ballSpeedY, (float) pwm.ballSpeedX)));
            drawLine(g, ballPos, newPos);
            drawCircle(g, newPos, ballR);
        }
    }

    private void drawIP(PlayerWorldModel pwm, Graphics g) {
        float ballR = Param.BALL_SIZE * this.ballMagnify;
        g.setColor(Color.RED);
        fillCircle(g, new Point2f((float) pwm.ballInterceptY, (float) pwm.ballInterceptX), ballR);
    }

    private void drawOppIP(PlayerWorldModel pwm, Graphics g) {
        float ballR = Param.BALL_SIZE * this.ballMagnify;
        g.setColor(Color.BLUE);
        fillCircle(g, new Point2f((float) pwm.oppInterceptY, (float) pwm.oppInterceptX), ballR);
    }

    private void drawOffsideLine(PlayerWorldModel pwm, Graphics g) {
        g.setColor(Color.BLUE);

        this.upperOffside = new Point2f((float) pwm.offsideY, Param.PITCH_WIDTH / 2);
        this.lowerOffside = new Point2f((float) pwm.offsideY, -Param.PITCH_WIDTH / 2);

        drawLine(g, this.upperOffside, this.lowerOffside);
    }

}
